package han.chensing.latos.game.processor;

import org.json.JSONObject;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import han.chensing.latos.common.file.CirnoSerialize;
import han.chensing.latos.common.level.LatosProject;
import han.chensing.latos.common.level.content.CommonLine;
import han.chensing.latos.common.level.content.CommonNote;
import han.chensing.latos.common.level.timeLine.TimeLineEventSet;
import han.chensing.latos.common.level.util.StreamUtil;
import han.chensing.latos.game.elements.notes.DirectFlickNote;
import han.chensing.latos.game.elements.notes.DragNote;
import han.chensing.latos.game.elements.notes.FlickNote;
import han.chensing.latos.game.elements.notes.HoldNote;
import han.chensing.latos.game.elements.notes.Line;
import han.chensing.latos.game.elements.notes.Note;
import han.chensing.latos.game.elements.notes.TapNote;

public class LevelTranslator {

    @SuppressWarnings({"CharsetObjectCanBeUsed", "StringOperationCanBeSimplified"})
    public static Object[] translateWholeLevel(
            byte[] bytes)
            throws ClassNotFoundException, InvocationTargetException,
            InstantiationException, IllegalAccessException, NoSuchMethodException, IOException, NoSuchFieldException {
        ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
        ZipInputStream zipInputStream=new ZipInputStream(byteArrayInputStream);
        String json=null;
        byte[] musicBytes=null;
        ZipEntry zipEntry;
        while((zipEntry=zipInputStream.getNextEntry())!=null){
            String name = zipEntry.getName();
            if (name.equalsIgnoreCase("level.json")){
                ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
                StreamUtil.streamCopy(zipInputStream,byteArrayOutputStream);
                json=new String(byteArrayOutputStream.toByteArray(), "UTF8");
                byteArrayOutputStream.close();
            }else if (name.contains("music")){
                ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
                StreamUtil.streamCopy(zipInputStream,byteArrayOutputStream);
                musicBytes=byteArrayOutputStream.toByteArray();
                byteArrayOutputStream.close();
            }
            zipInputStream.closeEntry();
        }
        zipInputStream.close();
        byteArrayInputStream.close();
        if (musicBytes==null||json==null)return null;
        Object[] jsonTranslate = translate(json);
        return new Object[]{jsonTranslate[0],jsonTranslate[1],musicBytes};
    }

    public static Object[] translate(
            String jsonProject)
            throws ClassNotFoundException, InvocationTargetException,
            InstantiationException, IllegalAccessException,
            NoSuchMethodException, NoSuchFieldException {
        CirnoSerialize cirnoSerialize=new CirnoSerialize();
        JSONObject jsonObject=new JSONObject(jsonProject);
        Object object = cirnoSerialize.jsonToObject(jsonObject);
        return translate((LatosProject) object);
    }

    public static Object[] translate(LatosProject latosProject)
            throws InvocationTargetException, InstantiationException,
            IllegalAccessException, NoSuchMethodException {
        List<Line> lines=new ArrayList<>();
        float offset = latosProject.getLatosMetaData().getOffset();
        ArrayList<CommonLine> commonLines = latosProject.getLevelData().getLines();
        for (CommonLine commonLine : commonLines) {
            Line line = translateCommonLineToGameLine(offset,commonLine,
                    latosProject.getLevelData().getTimeLine().getTimeLineRows().get(commonLine.getNum()).getTimeLineEventSets().get(4));
            lines.add(line);
        }
        return new Object[]{latosProject,lines};
    }

    private static Line translateCommonLineToGameLine(
            float offset, CommonLine commonLine, TimeLineEventSet timeLineEventSet)
            throws InvocationTargetException,
            InstantiationException, IllegalAccessException,
            NoSuchMethodException {
        Line line = new Line(commonLine.getNum());
        for (CommonNote commonNote : commonLine.getNotes()) {
            CommonNote.NoteType noteType = commonNote.getNoteType();
            Constructor<? extends Note> constructor = getNoteConstructor(noteType);
            Note note = constructor.newInstance(line);
            applyNoteElementInfoToNote(commonNote, note,offset);
            note.finalInit();
            if (note.getNoteType() != Note.NoteType.Hold)
                line.getNormalNotes().add(note);
            else
                line.getHoldNotes().add((HoldNote) note);
        }
        return line;
    }

    private static Constructor<? extends Note> getNoteConstructor(CommonNote.NoteType noteType) throws NoSuchMethodException {
        Class<? extends Note> noteClass;
        switch (noteType) {
            default:
            case Tap:
                noteClass = TapNote.class;
                break;
            case Drag:
                noteClass = DragNote.class;
                break;
            case Hold:
                noteClass = HoldNote.class;
                break;
            case Flick:
                noteClass = FlickNote.class;
                break;
            case DirectFlick:
                noteClass = DirectFlickNote.class;
                break;
        }
        return noteClass.getConstructor(Line.class);
    }

    private static void applyNoteElementInfoToNote(CommonNote commonNote,Note note,float offset){
        note.setDistanceHeight(commonNote.getDistanceHeight());
        note.setDistanceToLineCenter(commonNote.getDistanceToLineCenter());
        note.setShown(commonNote.isShown());
        note.setShown(true);
        note.setHitTime((commonNote.getBindSample().getAbsoluteTime()-offset)/1000f);
        //note.setUp(commonNote.is);
        note.setBackGoing(commonNote.isBackGoing());
        note.setAlpha(1);
        if (note instanceof HoldNote){
            HoldNote holdNote = (HoldNote) note;
            holdNote.setEndHitTime(commonNote.getBindEndSample().getAbsoluteTime()/1000f);
        }
    }
}
